/**
 * 
 */
package de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.model;
import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.data.Word;
import de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.data.WordCorpus;
import de.uni_leipzig.informatik.asv.util.MixingIterator;
/**
 * 
 * @author Christoph Teichmann
 * created May 30, 2014 11:32:36 AM
 * @version 0.1
 */
public abstract class GenericModel implements Model
{
	/**
	 * 
	 * @param w
	 */
	public void initialize(Word w) 
	{
		int id = w.getIdentity();
		int language = w.getLanguage();
		double a = w.getAmount();
		this.initializeWord(w, id,language, a);
		for(int i=0;i<w.getNumberOfFeatures();++i)
		{
			int label = w.getFeatureName(i);
			double value = w.getFeatureValue(i);
			int group = this.data.getFeatureGroup(label);
			this.initializeFeature(w, label,group,value,id,language);
		}
		for(int i=0;i<w.getNumberOfConnections();++i)
		{
			int label = w.getConnectionLabel(i);
			Word other = w.getConnectedWord(i);
			double amount = w.getConnectionWeight(i);
			this.initializeConnections(w, label, other, amount);
		}
	}
	/**
	 * 
	 * @param w
	 * @param label
	 * @param other
	 * @param otherTag
	 * @param amount
	 */
	protected abstract void initializeConnections(Word w, int label, Word other, double amount);
	/**
	 * 
	 * @param w
	 * @param label
	 * @param group
	 * @param value
	 * @param id
	 * @param language
	 */
	protected abstract void initializeFeature(Word w, int label, int group, double value,
			int id, int language);
	/**
	 * 
	 * @param w
	 * @param id
	 * @param language
	 * @param a
	 */
	protected abstract void initializeWord(Word w, int id, int language, double a);
	/**
	 * @param data
	 */
	public GenericModel(WordCorpus data)
	{this.data = data;}
	/**
	 * 
	 */
	private boolean intialized = false;
	/**
	 * 
	 */
	public void initialize()
	{
		if(!this.intialized)
		{
			this.intialized  = true;
			for(Word w : this.data)
			{initialize(w);}
		}
	}
	/**
	 * 
	 */
	private final WordCorpus data;
	/**
	 * 
	 */
	private final Map<Word,double[]> values = new HashMap<>();
	/* (non-Javadoc)
	 * @see java.lang.Iterable#iterator()
	 */
	@Override
	public Iterator<Word> iterator()
	{return this.data.iterator();}
	/* (non-Javadoc)
	 * @see de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.model.Model#isInitialized(de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.data.Word)
	 */
	@Override
	public boolean isInitialized(Word w)
	{return this.values.containsKey(w);}
	/* (non-Javadoc)
	 * @see de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.model.Model#makeLogProbability(de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.data.Word)
	 */
	@Override
	public double makeLogProbability(Word w, int choice)
	{
		int id = w.getIdentity();
		int language = w.getLanguage();
		double a = w.getAmount();
		double logProb = this.makeWordLogProb(w, id,language, choice, a);
		for(int i=0;i<w.getNumberOfFeatures();++i)
		{
			int label = w.getFeatureName(i);
			double value = w.getFeatureValue(i);
			int group = this.data.getFeatureGroup(label);
			logProb += this.makeFeatureLogProb(w, choice, label,group,value,id,language);
		}
		for(int i=0;i<w.getNumberOfConnections();++i)
		{
			int label = w.getConnectionLabel(i);
			Word other = w.getConnectedWord(i);
			int otherTag = this.getTag(other);
			if(otherTag < 0)
			{continue;}
			double amount = w.getConnectionWeight(i);
			logProb += this.makeConnectionsLogProb(w, choice, label, other, otherTag, amount);			
		}
		for(int i=0;i<w.getNumberOfReverseConnections();++i)
		{
			int label = w.getReverseConnectionLabel(i);
			Word other = w.getReverseConnectedWord(i);
			int otherTag = this.getTag(other);
			if(otherTag < 0)
			{continue;}
			double amount = w.getReverseConnectionWeight(i);
			logProb += this.makeConnectionsLogProb(other, otherTag, label, w, choice, amount);			
		}
		return logProb+additionalLogProb(w);
	}
	/**
	 *
	 * @param w
	 * @param choice
	 * @param d
	 */
	protected void add(Word w, int choice, double multiplier)
	{
		int id = w.getIdentity();
		int language = w.getLanguage();
		double a = w.getAmount();
		this.addWord(w,id,choice,language,a,multiplier);
		for(int i=0;i<w.getNumberOfFeatures();++i)
		{
			int label = w.getFeatureName(i);
			double value = w.getFeatureValue(i);
			int group = this.data.getFeatureGroup(label);
			this.addFeature(w, choice,label,group,id,language,value,multiplier);
		}
		for(int i=0;i<w.getNumberOfConnections();++i)
		{
			int label = w.getConnectionLabel(i);
			Word other = w.getConnectedWord(i);
			int otherTag = this.getTag(other);
			if(otherTag < 0)
			{continue;}
			double amount = w.getConnectionWeight(i);
			this.addConnections(w,choice,label, other,otherTag,amount,multiplier);
		}
		for(int i=0;i<w.getNumberOfReverseConnections();++i)
		{
			int label = w.getReverseConnectionLabel(i);
			Word other = w.getReverseConnectedWord(i);
			int otherTag = this.getTag(other);
			if(otherTag < 0)
			{continue;}
			double amount = w.getReverseConnectionWeight(i);
			this.addConnections(other, otherTag, label, w, choice, amount, multiplier);
		}
		additionalAdding(w,choice);
	}
	/**
	 *
	 * @param w
	 * @return
	 */
	protected void additionalAdding(Word w, int choice)
	{}
	/**
	 *
	 * @param w
	 * @param choice
	 * @param label
	 * @param group
	 * @param other
	 * @param otherTag
	 * @param multiplier
	 * @return
	 */
	protected abstract void addConnections(Word w, int choice, int label,
							Word other, int otherTag, double amount, double multiplier);
	/**
	 *
	 * @param w
	 * @param choice
	 * @param label
	 * @param group
	 * @param id
	 * @param language
	 * @param multiplier
	 * @return
	 */
	protected abstract void addFeature(Word w, int choice, int label, int group, int id,
								int language,double amount, double multiplier);
	/**
	 *
	 * @param w
	 * @param id
	 * @param language
	 * @param multiplier
	 * @param multiplier 
	 * @return
	 */
	protected abstract void addWord(Word w, int id, int choice,
								int language, double amount, double multiplier);
	/**
	 *
	 * @param choice
	 * @param label
	 * @param group
	 * @param otherWordLanguage
	 * @param otherWord
	 * @param otherTag
	 * @param amount
	 * @return
	 */
	protected abstract double makeConnectionsLogProb(Word w,int choice, int label,
														Word other, int otherTag, double amount);
	/**
	 *
	 * @param otherWord
	 * @param otherWordLanguage
	 * @return
	 */
	@Override
	public int getTag(Word w)
	{return this.assignments.getInt(w);}
	/**
	 * 
	 */
	private final Object2IntOpenHashMap<Word> assignments = new Object2IntOpenHashMap<>();
	{assignments.defaultReturnValue(-1);}
	/**
	 *
	 * @param w
	 * @return
	 */
	protected double additionalLogProb(Word w)
	{return 0;}
	/**
	 *
	 * @param id
	 * @param language
	 * @param w
	 * @param a 
	 * @return
	 */
	protected abstract double makeWordLogProb(Word w, int id, int language, int choice, double a);
	/**
	 * @return the data
	 */
	public WordCorpus getData()
	{return data;}
	/**
	 *
	 * @param label
	 * @param group
	 * @param value
	 * @param language 
	 * @param id 
	 * @param w 
	 * @param data2
	 * @return
	 */
	protected abstract double makeFeatureLogProb(Word w,int choice, int label, int group,
										double value, int id, int language);
	/* (non-Javadoc)
	 * @see de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.model.Model#setTag(de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.data.Word, int, double)
	 */
	@Override
	public void setTag(Word w, int choice)
	{
		if(!this.assignments.containsKey(w))
		{this.add(w,choice,1.0);}
		else
		{
			this.add(w, this.assignments.getInt(w), -1.0);
			this.add(w, choice, 1.0);
		}
		this.assignments.put(w, choice);
	}
	/**
	 *
	 * @return
	 */
	protected abstract int getLengthGuess();
	/* (non-Javadoc)
	 * @see de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.model.Model#mIterator()
	 */
	@Override
	public MixingIterator<Word> mIterator()
	{return this.data.mIterator();}
	/**
	 *
	 * @param lang
	 * @return
	 */
	protected abstract int getCurrentMaxNumberOfLabelsForLanguage(int lang);
	/* (non-Javadoc)
	 * @see de.uni_leipzig.informatik.asv.unsupervised_pos_tagger.model.Model#writeOutTags(java.io.BufferedReader)
	 */
	@Override
	public void writeOutTags(BufferedWriter bw) throws IOException
	{
		Iterator<Entry<Word>> it = this.assignments.object2IntEntrySet().fastIterator();
		boolean first = true;
		while(it.hasNext())
		{
			Entry<Word> wt = it.next();
			if(first)
			{first = false;}
			else
			{bw.newLine();}
			Word w = wt.getKey();
			bw.write("Language: "+this.data.getLanguageName(w.getLanguage())+" Word: "+
						this.data.getName(w.getIdentity())+
						" tag: "+Integer.toString(this.assignments.getInt(w)));
		}
		bw.flush();
	}
}